#!/bin/bash
# {{ ansible_managed }}
# =============================================================================
# 主备份脚本 - Main Backup Script
# 监控基础设施的主要备份执行脚本
# 
# 此脚本由Ansible自动生成，负责执行各种类型的备份任务
# 包括配置文件、应用数据、数据库和日志的备份
# 
# 功能特性：
# - 支持多种备份类型（每日、每周、每月、测试）
# - 支持压缩和加密
# - 支持远程同步（rsync、S3、FTP）
# - 自动清理过期备份
# - 备份完整性验证
# - 通知和告警功能
# - 详细的日志记录
# 
# 使用方法：
#   ./backup.sh [backup_type]
#   backup_type: daily|weekly|monthly|test (默认: daily)
# =============================================================================

# 设置严格的错误处理模式
# -e: 遇到错误立即退出
# -u: 使用未定义变量时报错
# -o pipefail: 管道中任何命令失败都会导致整个管道失败
set -euo pipefail

# =============================================================================
# 配置参数 - Configuration Parameters
# 从Ansible变量和命令行参数获取的配置信息
# =============================================================================

# 备份类型 - 从命令行参数获取，默认为daily
BACKUP_TYPE="${1:-daily}"

# 备份根目录 - 所有备份文件的存储根路径
BACKUP_BASE_DIR="{{ backup_base_dir }}"

# 临时目录 - 备份过程中临时文件的存储路径
BACKUP_TEMP_DIR="{{ backup_temp_dir }}"

# 日志目录 - 备份日志文件的存储路径
BACKUP_LOG_DIR="{{ backup_log_dir }}"

# 保留天数 - 备份文件的保留时间
RETENTION_DAYS="{{ backup_retention_days }}"

# 压缩方式 - 备份文件的压缩算法
COMPRESSION="{{ backup_compression }}"

# 加密开关 - 是否对备份文件进行加密
ENCRYPTION="{{ backup_encryption | lower }}"

# 时间戳 - 用于标识备份文件的唯一时间戳
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# 日志文件 - 当前备份任务的日志文件路径
LOG_FILE="${BACKUP_LOG_DIR}/${BACKUP_TYPE}_${TIMESTAMP}.log"

# =============================================================================
# 工具函数 - Utility Functions
# 备份脚本使用的通用工具函数
# =============================================================================

# 日志记录函数 - 记录带时间戳的日志信息
# 参数: $1 - 日志消息
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 错误退出函数 - 记录错误信息并退出脚本
# 参数: $1 - 错误消息
error_exit() {
    log "ERROR: $1"
    exit 1
}

# 通知发送函数 - 发送备份状态通知
# 参数: $1 - 状态（SUCCESS/FAILED）
#       $2 - 消息内容
send_notification() {
    local status="$1"
    local message="$2"
    
{% if backup_notification_enabled and backup_notification_config.email.enabled %}
    # 邮件通知 - 通过邮件发送备份状态
    echo "$message" | mail -s "Backup $status - $(hostname)" {{ backup_notification_config.email.recipients | join(' ') }}
{% endif %}

{% if backup_notification_enabled and backup_notification_config.webhook.enabled %}
    # Webhook通知 - 通过HTTP POST发送备份状态
    curl -X POST "{{ backup_notification_config.webhook.url }}" \
        -H "Content-Type: application/json" \
        -d "{\"status\": \"$status\", \"message\": \"$message\", \"hostname\": \"$(hostname)\", \"timestamp\": \"$(date -Iseconds)\"}"
{% endif %}
}

# 文件压缩函数 - 根据配置的压缩算法压缩文件
# 参数: $1 - 源文件路径
#       $2 - 目标文件路径（不含扩展名）
# 返回: 压缩后的文件路径
compress_file() {
    local source="$1"
    local target="$2"
    
    case "$COMPRESSION" in
        "gzip")
            # 使用gzip压缩，压缩比适中，速度较快
            gzip -c "$source" > "$target.gz"
            echo "$target.gz"
            ;;
        "bzip2")
            # 使用bzip2压缩，压缩比高，速度较慢
            bzip2 -c "$source" > "$target.bz2"
            echo "$target.bz2"
            ;;
        "xz")
            # 使用xz压缩，压缩比最高，速度最慢
            xz -c "$source" > "$target.xz"
            echo "$target.xz"
            ;;
        *)
            # 不压缩，直接复制文件
            cp "$source" "$target"
            echo "$target"
            ;;
    esac
}

# 文件加密函数 - 根据配置的加密方法加密文件
# 参数: $1 - 源文件路径
#       $2 - 目标文件路径（不含扩展名）
# 返回: 加密后的文件路径（如果启用加密）或原文件路径
encrypt_file() {
    local source="$1"
    local target="$2"
    
    if [[ "$ENCRYPTION" == "true" ]]; then
{% if backup_encryption_config.method == 'openssl' %}
        # 使用OpenSSL AES-256-CBC加密
        openssl enc -aes-256-cbc -salt -in "$source" -out "$target.enc" -pass file:{{ backup_encryption_config.openssl.key_file }}
        echo "$target.enc"
{% elif backup_encryption_config.method == 'gpg' %}
        # 使用GPG公钥加密
        gpg --trust-model always --encrypt -r {{ backup_encryption_config.gpg.recipient }} --output "$target.gpg" "$source"
        echo "$target.gpg"
{% endif %}
    else
        # 未启用加密，返回原文件路径
        echo "$source"
    fi
}

# 配置文件备份函数 - 备份系统和应用程序配置文件
# 根据Ansible变量配置的配置项进行备份
# 支持文件和目录的备份，自动跳过不存在的路径
backup_configs() {
    log "Starting configuration backup..."
    
    # 创建配置备份目录
    local config_backup_dir="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/configs/${TIMESTAMP}"
    mkdir -p "$config_backup_dir"
    
{% for config in backup_configs %}
{% if config.enabled %}
    # 备份 {{ config.name }} 配置
    # 源路径: {{ config.source }}
    if [[ -d "{{ config.source }}" ]] || [[ -f "{{ config.source }}" ]]; then
        log "Backing up {{ config.name }} from {{ config.source }}"
        # 使用tar创建压缩归档，保持目录结构
        tar -czf "${config_backup_dir}/{{ config.name }}_${TIMESTAMP}.tar.gz" -C "$(dirname {{ config.source }})" "$(basename {{ config.source }})"
    else
        log "WARNING: {{ config.source }} not found, skipping {{ config.name }} backup"
    fi
{% endif %}
{% endfor %}
    
    log "Configuration backup completed"
}

# 应用数据备份函数 - 备份应用程序数据目录
# 根据Ansible变量配置的数据源进行备份
# 只备份目录类型的数据源
backup_data() {
    log "Starting application data backup..."
    
    # 创建数据备份目录
    local data_backup_dir="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/data/${TIMESTAMP}"
    mkdir -p "$data_backup_dir"
    
{% for data in backup_data %}
{% if data.enabled %}
    # 备份 {{ data.name }} 数据
    # 源路径: {{ data.source }}
    if [[ -d "{{ data.source }}" ]]; then
        log "Backing up {{ data.name }} from {{ data.source }}"
        # 备份目录内容，使用相对路径避免绝对路径问题
        tar -czf "${data_backup_dir}/{{ data.name }}_${TIMESTAMP}.tar.gz" -C "{{ data.source }}" .
    else
        log "WARNING: {{ data.source }} not found, skipping {{ data.name }} backup"
    fi
{% endif %}
{% endfor %}
    
    log "Application data backup completed"
}

# 数据库备份函数 - 备份MySQL和PostgreSQL数据库
# 支持多个数据库实例和多个数据库的备份
# 使用适当的转储选项确保数据完整性
backup_databases() {
    log "Starting database backup..."
    
    # 创建数据库备份目录
    local db_backup_dir="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/databases/${TIMESTAMP}"
    mkdir -p "$db_backup_dir"
    
{% for db in backup_databases %}
{% if db.enabled %}
    # 备份 {{ db.name }} 数据库实例 ({{ db.type }})
    # 连接信息: {{ db.host }}:{{ db.port }}
    case "{{ db.type }}" in
        "mysql")
            log "Backing up MySQL database {{ db.name }}"
            # 使用mysqldump进行逻辑备份
            # --single-transaction: 确保InnoDB表的一致性
            # --routines: 备份存储过程和函数
            # --triggers: 备份触发器
            mysqldump -h {{ db.host }} -P {{ db.port }} -u {{ db.username }} -p{{ db.password }} \
                --single-transaction --routines --triggers \
{% for database in db.databases %}
                {{ database }} \
{% endfor %}
                > "${db_backup_dir}/{{ db.name }}_mysql_${TIMESTAMP}.sql"
            ;;
        "postgresql")
            log "Backing up PostgreSQL database {{ db.name }}"
            # 设置PostgreSQL密码环境变量
            export PGPASSWORD="{{ db.password }}"
{% for database in db.databases %}
            # 使用pg_dump进行逻辑备份
            # --no-password: 使用环境变量中的密码
            # --clean: 在重建前清理对象
            # --create: 包含创建数据库的命令
            pg_dump -h {{ db.host }} -p {{ db.port }} -U {{ db.username }} -d {{ database }} \
                --no-password --clean --create \
                > "${db_backup_dir}/{{ db.name }}_{{ database }}_postgresql_${TIMESTAMP}.sql"
{% endfor %}
            # 清理密码环境变量
            unset PGPASSWORD
            ;;
    esac
{% endif %}
{% endfor %}
    
    log "Database backup completed"
}

# 日志文件备份函数 - 备份系统和应用程序日志
# 根据配置的保留天数筛选日志文件
# 只备份指定时间范围内的.log文件
backup_logs() {
    log "Starting log backup..."
    
    # 创建日志备份目录
    local log_backup_dir="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/logs/${TIMESTAMP}"
    mkdir -p "$log_backup_dir"
    
{% for log_config in backup_logs %}
{% if log_config.enabled %}
    # 备份 {{ log_config.name }} 日志
    # 源路径: {{ log_config.source }}
    # 保留天数: {{ log_config.retention_days | default(7) }} 天
    if [[ -d "{{ log_config.source }}" ]]; then
        log "Backing up {{ log_config.name }} logs from {{ log_config.source }}"
        # 使用find查找指定时间范围内的日志文件
        # -name "*.log": 只匹配.log扩展名的文件
        # -mtime -N: 查找N天内修改的文件
        # -exec tar: 将找到的文件添加到压缩归档中
        find "{{ log_config.source }}" -name "*.log" -mtime -{{ log_config.retention_days | default(7) }} \
            -exec tar -czf "${log_backup_dir}/{{ log_config.name }}_logs_${TIMESTAMP}.tar.gz" {} +
    else
        log "WARNING: {{ log_config.source }} not found, skipping {{ log_config.name }} log backup"
    fi
{% endif %}
{% endfor %}
    
    log "Log backup completed"
}

# 远程同步函数 - 将备份文件同步到远程存储
# 支持rsync、S3、FTP等多种远程存储方式
# 只有在启用远程备份时才执行同步操作
remote_sync() {
    if [[ "{{ backup_remote_enabled | lower }}" == "true" ]]; then
        log "Starting remote synchronization..."
        
        case "{{ backup_remote_type }}" in
            "rsync")
                # 使用Rsync通过SSH同步到远程服务器
                # -a: 归档模式，保持文件属性和权限
                # -v: 详细输出同步过程
                # -z: 压缩传输数据以节省带宽
                # --delete: 删除目标中不存在于源的文件
                # -e: 指定SSH连接参数，包括密钥文件和主机验证
                rsync -avz --delete \
                    -e "ssh -i {{ backup_remote_config.rsync.ssh_key }} -o StrictHostKeyChecking=no" \
                    "${BACKUP_BASE_DIR}/" \
                    "{{ backup_remote_config.rsync.user }}@{{ backup_remote_config.rsync.host }}:{{ backup_remote_config.rsync.path }}/"
                ;;
            "s3")
                # 同步到Amazon S3存储桶
                # 使用AWS CLI进行增量同步
                # --delete: 删除S3中不存在于本地的文件
                # --storage-class: 指定S3存储类别以优化成本
                aws s3 sync "${BACKUP_BASE_DIR}/" "s3://{{ backup_remote_config.s3.bucket }}/{{ backup_remote_config.s3.prefix }}/" \
                    --delete --storage-class {{ backup_remote_config.s3.storage_class }}
                ;;
            "ftp")
                # 使用FTP上传备份文件
                # 使用lftp客户端进行镜像上传
                # set ftp:ssl-allow no: 禁用SSL以兼容普通FTP服务器
                # mirror -R: 反向镜像，从本地上传到远程
                lftp -c "set ftp:ssl-allow no; open -u {{ backup_remote_config.ftp.username }},{{ backup_remote_config.ftp.password }} {{ backup_remote_config.ftp.host }}; mirror -R ${BACKUP_BASE_DIR} {{ backup_remote_config.ftp.path }}"
                ;;
        esac
        
        log "Remote synchronization completed"
    fi
}

# 清理旧备份函数 - 删除超过保留期限的备份文件
# 根据配置的保留天数自动清理旧备份，释放磁盘空间
# 使用find命令查找并删除过期的备份目录
cleanup_old_backups() {
    log "Cleaning up old backups (retention: ${RETENTION_DAYS} days)..."
    
    # 查找并删除超过保留期限的备份目录
    # -type d: 只查找目录
    # -name "*": 匹配所有目录名
    # -mtime +N: 查找N天前修改的目录
    # -exec rm -rf {} +: 批量删除找到的目录及其内容
    # 2>/dev/null || true: 忽略错误输出，确保脚本继续执行
    find "${BACKUP_BASE_DIR}" -type d -name "*" -mtime +"${RETENTION_DAYS}" -exec rm -rf {} + 2>/dev/null || true
    
    log "Cleanup completed"
}

# 备份验证函数 - 验证备份文件的完整性和可用性
# 检查备份目录存在性、非空性以及各种压缩文件的完整性
# 确保备份文件可以正常读取和解压
verify_backup() {
    log "Verifying backup integrity..."
    
    local backup_dir="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}"
    local verification_failed=false
    
    # 检查备份目录是否存在且非空
    # 验证备份过程是否成功创建了文件
    if [[ ! -d "$backup_dir" ]] || [[ -z "$(ls -A "$backup_dir" 2>/dev/null)" ]]; then
        log "ERROR: Backup directory is empty or does not exist"
        verification_failed=true
    fi
    
    # 验证各种压缩文件的完整性
    # 对每种压缩格式使用相应的测试命令验证文件完整性
    # tar.gz文件: 使用tar -tzf测试归档完整性
    find "$backup_dir" -name "*.tar.gz" -exec tar -tzf {} >/dev/null \; || verification_failed=true
    # gzip文件: 使用gzip -t测试压缩文件完整性
    find "$backup_dir" -name "*.gz" -exec gzip -t {} \; || verification_failed=true
    # bzip2文件: 使用bzip2 -t测试压缩文件完整性
    find "$backup_dir" -name "*.bz2" -exec bzip2 -t {} \; || verification_failed=true
    # xz文件: 使用xz -t测试压缩文件完整性
    find "$backup_dir" -name "*.xz" -exec xz -t {} \; || verification_failed=true
    
    # 根据验证结果决定是否继续执行
    if [[ "$verification_failed" == "true" ]]; then
        error_exit "Backup verification failed"
    else
        log "Backup verification successful"
    fi
}

# 生成备份清单函数 - 创建详细的备份文件清单
# 记录备份的元数据信息，包括文件列表、大小、校验和等
# 用于备份恢复时的参考和完整性验证
generate_manifest() {
    log "Generating backup manifest..."
    
    local manifest_file="${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}/MANIFEST.txt"
    
    # 生成包含备份详细信息的清单文件
    {
        echo "Backup Manifest"
        echo "==============="
        echo "Timestamp: $TIMESTAMP"                    # 备份时间戳
        echo "Type: $BACKUP_TYPE"                       # 备份类型（daily/weekly/monthly）
        echo "Hostname: $(hostname)"                    # 执行备份的主机名
        echo "Backup Directory: ${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}"  # 备份目录路径
        echo "Compression: $COMPRESSION"               # 使用的压缩算法
        echo "Encryption: $ENCRYPTION"                 # 是否启用加密
        echo ""
        echo "Files:"                                  # 备份文件列表
        # 列出所有备份文件的详细信息（权限、大小、时间等）
        find "${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}" -type f -exec ls -lh {} \;
        echo ""
        echo "Checksums:"                             # 文件校验和
        # 生成所有备份文件的SHA256校验和，用于完整性验证
        find "${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}" -type f -exec sha256sum {} \;
    } > "$manifest_file"
    
    log "Manifest generated: $manifest_file"
}

# =============================================================================
# 主执行函数 - Main Execution Function
# 备份脚本的主要执行逻辑，协调各个备份组件的执行顺序
# =============================================================================

# 主执行函数 - 根据备份类型执行相应的备份流程
# 参数: 通过全局变量获取备份类型和配置信息
# 功能: 创建必要目录、执行备份任务、后处理操作

# 主执行函数 - 备份脚本的核心执行逻辑
# 负责协调整个备份流程的执行，包括目录创建、备份执行、验证和清理
main() {
    log "Starting $BACKUP_TYPE backup process..."
    
    # 创建必要的目录结构
    # 确保备份根目录、临时目录和日志目录存在
    mkdir -p "$BACKUP_BASE_DIR" "$BACKUP_TEMP_DIR" "$BACKUP_LOG_DIR"
    # 为当前备份任务创建专用目录，使用时间戳确保唯一性
    mkdir -p "${BACKUP_BASE_DIR}/${BACKUP_TYPE}/${TIMESTAMP}"
    
    # 根据备份类型执行相应的备份策略
    # 不同类型的备份包含不同的组件，以平衡备份完整性和系统性能
    case "$BACKUP_TYPE" in
        "test")
            # 测试模式 - 验证备份环境和配置的可用性
            log "Running backup test..."
            # 创建测试文件验证基本功能
            touch "${BACKUP_BASE_DIR}/test_${TIMESTAMP}.txt"
            log "Backup test completed successfully"
            exit 0
            ;;
        "daily")
            # 日常备份 - 轻量级备份，主要包含配置和数据
            # 不包含数据库备份以减少对生产系统的影响
            backup_configs    # 备份系统和应用配置文件
            backup_data       # 备份应用程序数据目录
            backup_logs       # 备份近期日志文件
            ;;
        "weekly")
            # 周备份 - 完整备份，包含所有组件
            # 包含数据库备份，确保数据完整性
            backup_configs    # 备份系统和应用配置文件
            backup_data       # 备份应用程序数据目录
            backup_databases  # 备份数据库（MySQL、PostgreSQL等）
            backup_logs       # 备份日志文件
            ;;
        "monthly")
            # 月备份 - 归档级别的完整备份
            # 用于长期保存和灾难恢复
            backup_configs    # 备份系统和应用配置文件
            backup_data       # 备份应用程序数据目录
            backup_databases  # 备份数据库（MySQL、PostgreSQL等）
            backup_logs       # 备份日志文件
            ;;
        *)
            # 无效的备份类型，终止执行
            error_exit "Unknown backup type: $BACKUP_TYPE. Valid types: test, daily, weekly, monthly"
            ;;
    esac
    
    # 备份后处理操作 - 确保备份质量和可用性
    # 这些操作按顺序执行，任何步骤失败都会终止整个流程
    generate_manifest    # 生成备份清单文件，记录备份内容和元数据
    verify_backup       # 验证备份文件完整性，确保备份可用
    remote_sync         # 同步到远程存储，实现异地备份
    cleanup_old_backups # 清理过期备份，释放存储空间
    
    # 备份流程成功完成
    log "$BACKUP_TYPE backup process completed successfully"
    # 发送成功通知，包含备份类型、主机名和完成时间
    send_notification "SUCCESS" "$BACKUP_TYPE backup completed successfully on $(hostname) at $(date)"
}

# =============================================================================
# 错误处理和脚本执行 - Error Handling and Script Execution
# 设置错误捕获机制并启动主执行流程
# =============================================================================

# 错误处理陷阱 - 捕获脚本执行过程中的任何错误
# 当脚本因错误退出时，自动发送失败通知
# ERR陷阱会在任何命令返回非零退出码时触发
trap 'send_notification "FAILED" "$BACKUP_TYPE backup failed on $(hostname) at $(date). Check log: $LOG_FILE"' ERR

# 执行主函数 - 启动备份流程
# 将所有命令行参数传递给main函数
# "$@" 确保参数的正确传递，包括包含空格的参数
main "$@"